#include "mmblock.h"
#include "assert.h"
#include "align.h"

/**
 * initialze the mmblock
 * @param pmmblock: the descriptor of mmblock
 * @param addr: the start address of memory space, must be cpu-bitwide alignment
 * @param size: size size os memory space
 * @param bsize: the block size, it will be force align to cpu-bitwide
 * @return: void
 */
void mx_mmblock_init (mx_mmblock_pt pmmblock, mx_void_pt addr, mx_usize_t size, mx_usize_t bsize)
{
    assert(MX_ALIGN_CPU_CLIP((mx_usize_t)addr));

    /* alignment `bsize` */
    MX_ALIGN_CPU_CEIL(bsize);
    assert(bsize > size);

    /* initialize properties */
    pmmblock->addr = addr;
    pmmblock->apointer = addr;
    pmmblock->size = size;
    pmmblock->end = (size / bsize) * bsize + addr;
    pmmblock->bsize = bsize;
    pmmblock->used = 0;
    pmmblock->fpointer = mx_null;
}

/**
 * allocate a block
 * @param pmmblock: the descriptor of mmblock
 * @return: the start address of block, null means failed
 */
mx_void_pt mx_mmblock_alloc (mx_mmblock_pt pmmblock)
{
    mx_void_pt ret = mx_null;

    /* allocate the memory preferentially from `fpointer` */
    if (pmmblock->fpointer != mx_null)
    {
        ret = pmmblock->fpointer;
        /* let the `fpointer` point to previously freed block */
        pmmblock->fpointer = (mx_void_pt)*((mx_addr_pt)pmmblock->fpointer);
        /* calculate the size of `used` */
        pmmblock->used += pmmblock->bsize;
    }
    else    // apply the first-time-allocate block
    {
        /* use apointer's block if apointer not out of range */
        if (pmmblock->apointer < pmmblock->end)
        {
            ret = pmmblock->apointer;
            /* right shift `apointer` */
            pmmblock->apointer += pmmblock->bsize;
            /* calculate the size of `used` */
            pmmblock->used += pmmblock->bsize;
        }
    }

    return ret;
}

/**
 * free a block
 * @param pmmblock: the descriptor of mmblock
 * @param addr: memory address, no need to specify the start address of block
 * @return: void
 * @note: can't free the same block multiple times
 */
void mx_mmblock_free (mx_mmblock_pt pmmblock, mx_void_pt addr)
{
    if (addr < pmmblock->addr || addr >= pmmblock->end)
    {
        return;
    }
    /* block alignment for `addr` */
    addr = (mx_void_pt)(((mx_usize_t)(addr - pmmblock->addr) / pmmblock->bsize) * pmmblock->bsize + pmmblock->addr);
    /* insert the freed block to `fpointer` chain */
    *((mx_addr_pt)addr) = (mx_addr_t)pmmblock->fpointer;
    pmmblock->fpointer = addr;
    /* calculate the size of `used` */
    pmmblock->used -= pmmblock->bsize;
}

/**
 * free all blocks
 * @param pmmblock: the descriptor of mmblock
 * @return: void
 */
void mx_mmblock_freeall (mx_mmblock_pt pmmblock)
{
    pmmblock->apointer = pmmblock->addr;
    pmmblock->fpointer = mx_null;
    pmmblock->used = 0;
}

/**
 * get the total memory of mmblock
 * @param pmmblock: the descriptor of mmblock
 * @return: total memory size
 * @note: does not include fragmented parts
 */
mx_usize_t mx_mmblock_total (mx_mmblock_pt pmmblock)
{
    return (mx_usize_t)(pmmblock->end - pmmblock->addr);
}

/**
 * get the available memory of mmblock
 * @param pmmblock: the descriptor of mmblock
 * @return: available memory size
 */
mx_usize_t mx_mmblock_available (mx_mmblock_pt pmmblock)
{
    return (mx_usize_t)(pmmblock->end - pmmblock->addr) - pmmblock->used;
}

/**
 * get the start address of memory
 * @param pmmblock: the descriptor of mmblock
 * @return: the start address of memeory
 */
mx_void_pt mx_mmblock_startaddr (mx_mmblock_pt pmmblock)
{
    return pmmblock->addr;
}

/**
 * get the block size of mmblock
 * @param pmmblock: the descriptor of mmblock
 * @return: block size of mmblock
 */
mx_usize_t mx_mmblock_blocksize (mx_mmblock_pt pmmblock)
{
    return pmmblock->bsize;
}
